﻿using System;
using RayDen.Library.Core;
using RayDen.Library.Core.Primitives;
using RayDen.Library.Data;
using RayDen.Library.Data.Imaging;
using RayDen.Library.Entity.Frames;
using RayDen.Library.Entity.Scene;

namespace RayDen.SmallPT.Light
{
    public class InfiniteLight : ILight
    {
        public const string InfiniteLightMapPath = "InfiniteLight.MapPath";

        public InfiniteLight(RgbSpectrumTexture tx)
        {
            tex = tx;
            shiftU = 0f;
            shiftV = 0f;
            gain = new RgbSpectrum(1.0f);
        }

        public InfiniteLight(FrameLightsource lightsource)
            : this(
                (RgbSpectrumTexture) (lightsource.Parameters.Get<string>(InfiniteLightMapPath).NameContains("hdr", "exr")
                                          ? ImageFactory.FreeImageLoadExrBitmap(lightsource.Parameters.Get<string>(InfiniteLightMapPath))
                                          : ImageFactory.FreeImageLoadBitmap(lightsource.Parameters.Get<string>(InfiniteLightMapPath))))
        {

        }

        public void SetGain(RgbSpectrum g)
        {
            gain = g;
        }

        public void SetShift(float su, float sv)
        {
            shiftU = su;
            shiftV = sv;
        }

        public virtual void Preprocess() { }


        public RgbSpectrum SkyLight = new RgbSpectrum(0.5f, 0.44f, 0.56602f);
        public RgbSpectrum Le(Vector dir)
        {
            float theta = Vector.SphericalTheta(ref dir);
            UV uv = new UV(Vector.SphericalPhi(ref dir) * MathLab.INVTWOPI + shiftU,
                           theta * MathLab.INVPI + shiftV);

            return (RgbSpectrum) (gain
                //* SkyLight;
                                  * tex.Sample(uv.U, uv.V));
        }

        protected RgbSpectrumTexture tex;
        public float shiftU, shiftV;
        protected RgbSpectrum gain;

        #region ILightsource Members

        public bool IsDelta
        {
            get
            {
                return false;
            }
        }
        public RgbSpectrum SamplePhoton(SceneGeometryInfo scene, float u0, float u1, float u2, float u3, float u4, out float pdf, out RayInfo ray)
        {
            // Choose two points p1 and p2 on scene bounding sphere
            var worldCenter = scene.BoundingSphereCenter;
            var worldRadius = scene.BoundingSphereRadius * 1.01f;

            var p1 = worldCenter + worldRadius * MC.UniformSampleSphere(u0, u1);
            var p2 = worldCenter + worldRadius * MC.UniformSampleSphere(u2, u3);

            // Construct ray between p1 and p2
            ray = new RayInfo(p1, (p2 - p1).Normalize(), 1e-4f, 1e4f);

            // Compute InfiniteAreaLight ray weight
            Vector toCenter = (worldCenter - p1).Normalize();
            var rayDir = ray.Dir;
            float costheta = MathLab.AbsDot(ref toCenter, ref rayDir);
            pdf = costheta / (4f * MathLab.M_PI * MathLab.M_PI * worldRadius * worldRadius);

            return Le(ray.Dir);
        }
        public RgbSpectrum Sample(ref Point point, ref Normal n, float u0, float u1, float u2, out RayInfo ray, out float pdf)
        {
            var wi = MC.CosineSampleHemisphere(u1, u2);
            pdf = wi.z * MathLab.INVPI;

            Vector v1, v2;
            Vector.CoordinateSystem(n.ToVec(), out v1, out v2);

            wi = new Vector(
                v1.x * wi.x + v2.x * wi.y + n.x * wi.z,
                v1.y * wi.x + v2.y * wi.y + n.y * wi.z,
                v1.z * wi.x + v2.z * wi.y + n.z * wi.z);
            ray = new RayInfo(point, wi, 1e-4f, float.MaxValue);

            return Le(wi);
        }

        #endregion

        public string Name
        {
            get { return this.GetType().Name+Guid.NewGuid().ToString(); }
        }
    };


    public class PointLight : ILight
    {
        public string Name
        {
            get { return this.GetType().Name + Guid.NewGuid().ToString(); }
        }

        public Point Position;
        public RgbSpectrum Power;
        public float FalloffStart, FalloffEnd;

        public PointLight() { }
        public PointLight(FrameLightsource frameLight)
        {
            this.Position = (Point)frameLight.Parameters.Get<Vector>(FrameLightsource.PointLightPosition);
            this.Power = frameLight.Parameters.Get<RgbSpectrum>(FrameLightsource.LightsourcePower);
        }

        #region ILightsource Members
        public bool IsDelta
        {
            get
            {
                return true;
            }
        }

        public RgbSpectrum Le(Vector dir)
        {
            return Power / (Position - dir).Length2();
        }

        public RgbSpectrum Sample(ref Point point, ref Normal n, float u0, float u1, float u2, out RayInfo ray, out float pdf)
        {
            var dir = (Position - point);
            var l2 = dir.Length;
            dir.Normalize();
            pdf = MC.UniformSpherePdf();
            ray = new RayInfo(point, dir, 1e-4f, l2 - 1e-4f);
            return Power / l2;
        }


        public RgbSpectrum SamplePhoton(SceneGeometryInfo scene, float u0, float u1, float u2, float u3, float u4, out float pdf, out RayInfo ray)
        {
            float b0, b1, b2;
            Vector orig;

            Vector dir = MC.UniformSampleSphere(u0, u1);

            ray = new RayInfo(Position, dir, 1e-4f, 1e4f);

            pdf = MathLab.INV4PI;

            return Power * MathLab.M_PI * 4f;
        }

        #endregion
    }


    public class SphereAreaLight : ILight
    {
        public Point Position;
        public RgbSpectrum Power;
        public float FalloffStart, FalloffEnd, Radius;

        public SampledSpectrum Spectra;
        public string Name
        {
            get { return "SphereAreaLight" + Guid.NewGuid(); }
        }
        public SphereAreaLight() { }
        public SphereAreaLight(FrameLightsource frameLight)
        {
            this.Radius = 6f;
            this.Position = (Point)frameLight.Parameters.Get<Vector>(FrameLightsource.PointLightPosition);
            this.Power = frameLight.Parameters.Get<RgbSpectrum>(FrameLightsource.LightsourcePower) * 2.5f;
            this.Spectra = SPD_Data.FromSampled(SPD_Data.Al_k, SpectrumType.Illuminant);
            //new SampledSpectrum(ref this.Power, SpectrumType.Illuminant);
        }

        public bool IsDelta
        {
            get { return false; }
        }

        public RgbSpectrum Le(Vector dir)
        {
            return ((Power*Radius)/(Position - dir).Length2());
        }

        public RgbSpectrum Sample(ref Point point, ref Normal n, float u0, float u1, float u2, out RayInfo ray, out float pdf)
        {
            var l = Position - point + MC.UniformSampleSphere(u0, u1) * Radius;	// vector to random point on source
            var Dist = l.Length;		// distance to light source
            var Attenuation = 17f / Dist;	// distance attenuation of light


            pdf = MC.UniformSpherePdf();
            ray = new RayInfo(point, l / Dist, 1e-4f, Dist - 1e-4f);
            return (Power*Radius / Dist);
        }

        public RgbSpectrum SamplePhoton(SceneGeometryInfo scene, float u0, float u1, float u2, float u3, float u4, out float pdf, out RayInfo ray)
        {

            Vector dir = MC.UniformSampleSphere(u0, u1);

            ray = new RayInfo(Position+dir*Radius, dir, 1e-4f, 1e4f);

            pdf = MathLab.INV4PI;

            return (Power * Radius * MathLab.M_PI * 4f);
        }

        
    }
}